#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include "Define.h"
#include <pthread.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Core/Logger.h"
#include "MQTT/MQTTError.h"
#include "Core/Interface.h"
#include "Core/Utils/Number.h"
#include "../../Include/MQTT/Params.h"

typedef struct {
    int32_t         fd;                 /**socket fd*/
    int32_t         type;               /**socket type*/
    char*           host;               /**主机地址*/
    uint16_t        port;               /**端口*/
    uint32_t        connectTimeout;     /**连接超时时间,单位ms*/
} NetHandler;

typedef enum {
    readEvent  = 0x01,
    writeEvent = 0x02,
    errorEvent = 0x04,
} TransportEvent;

/**system 相关*/
static uint64_t systemTime();
static void freeMem(void* ptr);
static void systemSleep(int64_t time);
static void *mallocMem(uint32_t size);
static void systemRand(int8_t* out,int32_t outLen);

/**network 相关*/
static void* initialNet();
static int32_t netConnect(void* handle);
static int32_t deInitialNet(void** handle);
static int32_t setNetOptions(void* handle,NetOption option,void* data);
static int32_t transportWait(int32_t fd,int32_t event,int32_t* timeout);

static void disNetConnect(NetHandler* handler);
static int32_t netRecv(NetHandler *handle, uint8_t *buffer, uint32_t len, int32_t *readLen, uint32_t timeout);
static int32_t netSend(NetHandler *handle, uint8_t *buffer, uint32_t len, int32_t *sendLen, uint32_t timeout);

static int32_t
recvData(void *handle, uint8_t *buffer, uint32_t len, int32_t *readLen, uint32_t timeout, NetAddress *addr);
static int32_t
sendData(void *handle, uint8_t *buffer, uint32_t len, int32_t *sendLen, uint32_t timeout, NetAddress *addr);
static int32_t
networkConnect(char *host, uint16_t port, int32_t family, int32_t socketType, int32_t protocol, int32_t timeout,
               int32_t *socketFd);

/**lock 相关*/
static void* initialMutex();
static void lockMutex(void* handle);
static void unlockMutex(void* handle);
static void deInitialMutex(void** mutex);

/**
 * @brief 端口号转字符串，如端口号为80转为"80"
 * @param input     端口号
 * @param output    端口字符串
 */
static void portToString(uint16_t input, char *output) {
    uint8_t i = 0, j = 0;
    char temp[6] = {0};

    do {
        temp[i++] = input % 10 + '0';
    } while ((input /= 10) > 0);

    do {
        output[--i] = temp[j++];
    } while (i > 0);
}

static void *mallocMem(uint32_t size) {
    return malloc(size);
}

static void freeMem(void* ptr) {
    free(ptr);
}

static uint64_t systemTime() {

    struct timeval time;
    memset(&time, 0, sizeof(struct timeval));
    gettimeofday(&time, nullptr);
    return ((uint64_t)time.tv_sec * 1000 + (uint64_t)time.tv_usec / 1000);
}

static void systemSleep(int64_t time) {
    usleep(time * 1000);
}

static void systemRand(int8_t* out,int32_t outLen) {

    uint32_t idx = 0, bytes = 0, randNum = 0;
    struct timeval time;
    memset(&time, 0, sizeof(struct timeval));
    gettimeofday(&time, nullptr);
    srand((unsigned int)(time.tv_sec * 1000 + time.tv_usec / 1000) + rand());

    for (idx = 0; idx < outLen;) {
        if (outLen - idx < 4) {
            bytes = outLen - idx;
        } else {
            bytes = 4;
        }
        
        randNum = rand();
        while (bytes-- > 0) {
            out[idx++] = (uint8_t)(randNum >> bytes * 8);
        }
    }
}

static void* initialNet() {

    NetHandler* handler = malloc(sizeof(NetHandler));
    if (handler == nullptr) {
        return nullptr;
    }

    memset(handler,0x00, sizeof(NetHandler));
    handler->connectTimeout = 10*1000;
    handler->type = tcpClient;
    handler->fd = -1;
    return handler;
}

static int32_t deInitialNet(void** handle) {

    NetHandler* net = nullptr;
    if (handle == nullptr || *handle == nullptr) {
        return paramsIsNull;
    }

    net = *(NetHandler**)(handle);
    disNetConnect(net);

    if (net->host != nullptr) {
        free(net->host);
        net->host = nullptr;
    }

    free(net);
    *handle = nullptr;
    return aiotSuccess;
}

static int32_t setNetOptions(void* handle,NetOption option,void* data) {

    NetHandler* net = (NetHandler*)handle;
    if (handle == nullptr || data == nullptr) {
        return paramsIsNull;
    }

    switch (option) {
        case socketType:
            net->type = tcpClient;
            break;
        case netPort:
            net->port = *(uint16_t*)data;
            break;
        case netHost:
            net->host = malloc(strlen(data) + 1);
            if (net->host == nullptr) {
                return memoryMallocFailed;
            }

            memset(net->host,0x00,strlen(data) + 1);
            memcpy(net->host,data,strlen(data));
            break;
        case connectTimeout:
            net->connectTimeout = *(uint32_t *)data;
            break;
        case networkCred:
            break;
        default:
            return networkInvalidOption;
    }

    return aiotSuccess;
}

static int32_t netConnect(void* handle) {

    int32_t fd = -1;
    NetHandler* net = (NetHandler*)handle;
    if (handle == nullptr) {
        return paramsIsNull;
    }

    int32_t iRet = networkConnect(net->host, net->port, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, net->connectTimeout, &(net->fd));
    loggerOut(aiotInfo,__FILE__,__LINE__,"connect fd:%d\n", net->fd);
    if (iRet != aiotSuccess) {
        return iRet;
    }

    return iRet;
}

static int32_t recvData(void *handle, uint8_t *buffer, uint32_t len, int32_t *readLen, uint32_t timeout, NetAddress *addr) {

    NetHandler* net = (NetHandler*)handle;
    if (net->type == tcpClient) {
        return netRecv(net, buffer, len,readLen,timeout);
    }

    return networkRecvError;
}

static int32_t sendData(void *handle, uint8_t *buffer, uint32_t len, int32_t *sendLen, uint32_t timeout, NetAddress *addr) {

    NetHandler* net = (NetHandler*)handle;
    if (net->type == tcpClient) {
        return netSend(net, buffer, len,sendLen,timeout);
    }

    return networkSendError;
}

void* initialMutex() {

    int res = 0;
    pthread_mutex_t* mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
    if (nullptr == mutex) {
        return nullptr;
    }

    if (0 != (res = pthread_mutex_init(mutex, nullptr))) {
        perror("create mutex failed\n");
        free(mutex);
        return nullptr;
    }

    return (void*)mutex;
}

static void lockMutex(void* handle) {

    int32_t res = aiotSuccess;

    if (handle == nullptr) {
        return;
    }

    if (0 != (res = pthread_mutex_lock((pthread_mutex_t *)handle))) {
        printf("lock mutex failed: - '%s' (%d)\n", strerror(res), res);
    }
}

static void unlockMutex(void* handle) {

    int32_t res = aiotSuccess;
    if (handle == nullptr) {
        return;
    }

    if (0 != (res = pthread_mutex_unlock((pthread_mutex_t *)handle))) {
        printf("unlock mutex failed - '%s' (%d)\n", strerror(res), res);
    }
}

static void deInitialMutex(void** mutex) {

    if (mutex == nullptr || *mutex == nullptr) {
        return;
    }

    if (0 != pthread_mutex_destroy(*(pthread_mutex_t **)mutex)) {
        perror("destroy mutex failed\n");
    }

    free(*(pthread_mutex_t**)mutex);
    *mutex = nullptr;
}

static int32_t netRecv(NetHandler *handle, uint8_t *buffer, uint32_t len, int32_t *readLen, uint32_t timeout) {

    int32_t res = 0;
    int32_t recvBytes = 0;
    ssize_t recvRes = 0;
    uint64_t startAt = 0, nowTime = 0;
    int32_t selectAt = 0;

    struct timeval timeStart, timeNow;

    /** Start Time */
    gettimeofday(&timeStart,nullptr);
    startAt = timeStart.tv_sec * 1000 + timeStart.tv_usec / 1000;
    nowTime = startAt;

    do {

        gettimeofday(&timeNow,nullptr);
        nowTime = timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000;

        if (nowTime - startAt >= nowTime || timeout - (nowTime - startAt) > timeout) {
            break;
        }

        selectAt = timeout - (nowTime - startAt);

        res = transportWait(handle->fd,readEvent,&selectAt);
        if (res == 0) {
            continue;
        } else if (res < 0) {
            printf("network recv, errno: %d\n", errno);
            perror("network recv, nwk select failed: ");
            return networkSelectError;
        } else {

            recvRes = recv(handle->fd, buffer + recvBytes, len - recvBytes, 0);
            if (recvRes == 0) {
                printf("network recv, nwk connection closed\n");
                return networkClosed;
            } else if (recvRes < 0) {
                printf("network recv, errno: %d\n", errno);
                perror("network recv, nwk recv error: ");
                if (errno == EINTR) {
                    continue;
                }

                return networkRecvError;
            } else {
                recvBytes += recvRes;
                if (recvBytes == len) {
                    break;
                }
            }
        }

    } while (((nowTime - startAt) < timeout) && (recvBytes < len));

    *readLen = recvBytes;
    return aiotSuccess;
}

static int32_t netSend(NetHandler *handle, uint8_t *buffer, uint32_t len, int32_t *sendLen, uint32_t timeout) {

    int32_t res = 0;
    int32_t sendBytes = 0;
    ssize_t sendRes = 0;
    uint64_t startAt = 0, nowTime = 0;
    int32_t selectAt = 0;

    struct timeval timeStart, timeNow;
    /** Start Time */
    gettimeofday(&timeStart, nullptr);
    startAt = timeStart.tv_sec * 1000 + timeStart.tv_usec / 1000;
    nowTime = startAt;

    do {

        gettimeofday(&timeNow,nullptr);
        nowTime = timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000;

        if (nowTime - startAt >= nowTime || timeout - (nowTime - startAt) > timeout) {
            break;
        }

        selectAt = timeout - (nowTime - startAt);
        res = transportWait(handle->fd,writeEvent,&selectAt);
        if (res == 0) {
            printf("net send, nwk select timeout\n");
            continue;
        } else if (res < 0) {
            printf("net send, errno: %d\n", errno);
            perror("net send, nwk select failed: ");
            return networkSelectError;
        } else {
#if defined(MSG_NOSIGNAL)
            sendRes = send(handle->fd, buffer + sendBytes, len - sendBytes, MSG_NOSIGNAL);
#elif defined(SO_NOSIGPIPE)
            sendRes = send(handle->fd, buffer + sendBytes, len - sendBytes, SO_NOSIGPIPE);
#endif
            if (sendRes == 0) {
                printf("net send, nwk connection closed\n");
                return networkClosed;
            } else if (sendRes < 0) {
                printf("net send, errno: %d\n", errno);
                perror("net send, nwk recv error: ");
                if (errno == EINTR) {
                    continue;
                }

                return networkSelectError;
            } else {
                sendBytes += sendRes;
                if (sendBytes == len) {
                    break;
                }
            }
        }
    } while (((nowTime - startAt) < timeout) && (sendBytes < len));

    *sendLen = sendBytes;
    return aiotSuccess;
}

static void disNetConnect(NetHandler* handler) {

    loggerOut(aiotInfo,__FILE__,__LINE__,"disconnect fd=%d\n",handler->fd);
    if (handler->fd < 0) {
        return;
    }

    shutdown(handler->fd,2);
    close(handler->fd);
    handler->fd = -1;
}

/**
 * @brief 网络连接，支持ip和域名
 * @param host         域名或者ip,内部会进行域名解析
 * @param port
 * @param family
 * @param socketType
 * @param protocol
 * @param timeout
 * @param socketFd
 * @return
 */
static int32_t networkConnect(char *host, uint16_t port, int32_t family, int32_t socketType, int32_t protocol, int32_t timeout,int32_t *socketFd) {

    int32_t ret = 0;
    int32_t fdOut = -1,sockOption = 0;
    char service[6] = {0};
    struct addrinfo hints;
    struct addrinfo *addrInfo = nullptr,*pos = nullptr;

    memset(&hints,0, sizeof(struct addrinfo));
    hints.ai_family   = family;
    hints.ai_socktype = socketType;
    hints.ai_protocol = protocol;
    hints.ai_family   = 0;


    portToString(port,service);
    ret = getaddrinfo(host, service, &hints, &addrInfo);
    if (ret != 0) {
        freeaddrinfo(addrInfo);
        return networkDNSFailed;
    }

    for (pos = addrInfo; pos != nullptr; pos = pos->ai_next) {

        fdOut = socket(pos->ai_family, pos->ai_socktype, pos->ai_protocol);
        if (fdOut < 0) {
            printf("create socket error\n");
            ret = networkSocketCreateFailed;
            continue;
        }

        *socketFd = fdOut;
        ret = fcntl(fdOut, F_GETFL);
        if (ret != -1) {
            ret = fcntl(fdOut, F_SETFL, sockOption | O_NONBLOCK);
        }

        if (connect(fdOut, pos->ai_addr, pos->ai_addrlen) == 0) {
            break;
        }
        if (ret == -1 || (errno != EINPROGRESS && errno != EALREADY)) {
            return networkConnectFailed;
        }

        ret = transportWait(fdOut,writeEvent,&timeout);
        if (ret == 0) {
            /**timeout*/
            return networkWaitTimeout;
        } else if (ret < 0) {
            return networkSelectError;
        }

        ret = connect(fdOut, pos->ai_addr, pos->ai_addrlen);
        if ((ret != 0 && errno == EISCONN) || ret == 0) {
            ret = aiotSuccess;
            break;
        } else {
            return networkConnectFailed;
        }
    }

    struct sockaddr_in locAddr;
    socklen_t len = sizeof(sizeof(locAddr));
    char buf[1024] = {0};
    memset(&locAddr, 0, len);
    if (-1 == getsockname(fdOut, (struct sockaddr *)&locAddr, &len)) {
        /**获取socket绑定的本地address信息*/
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf), "get socket name failed. errno: %d, error: %s", errno, strerror(errno));
        perror(buf);
        exit(-1);
    }

    if (locAddr.sin_family == AF_INET) {
        printf("local port: %u\n", ntohs(locAddr.sin_port));
    }

    return ret;
}

static int32_t transportWait(int32_t fd,int32_t event,int32_t* timeout) {

    fd_set rfds;
    fd_set wfds;
    fd_set efds;

    struct timeval timeoutVal = {0,0};
    timeoutVal.tv_sec  = *timeout/1000;
    timeoutVal.tv_usec = (*timeout%1000)*1000;
    int32_t width = fd + 1;

    fd_set *pRfds,*pWfds,*pEfds;

    do {

        pRfds = pWfds = pEfds = nullptr;
        if ((event & readEvent) == readEvent) {
            FD_ZERO(&rfds);
            FD_SET(fd, &rfds);
            pRfds = &rfds;
        }

        if ((event & writeEvent) == writeEvent) {
            FD_ZERO(&wfds);
            FD_SET(fd, &wfds);
            pWfds = &wfds;
        }

        if ((event & errorEvent) == errorEvent) {
            FD_ZERO(&efds);
            FD_SET(fd, &efds);
            pEfds = &efds;
        }

        int32_t ret = select(width, pRfds, pWfds, pEfds, &timeoutVal);
        if (ret < 0 && EINTR != errno) {
            /// 系统调用出错.
            printf("select failed\n");
            return -1;
        } else if (0 == ret) {
            /// 超时处理.
            *timeout = 0;
            printf("wait event timeout\n");
            return 0;
        } else if (ret > 0) {
            /// 有事件发生
            *timeout = 1000 * timeoutVal.tv_sec + timeoutVal.tv_usec/1000;
            return 1;
        } else {
            /// select 被系统信号中断
            continue;
        }

    } while(1);
}

SystemInterface globalSystemInterface = {
        mallocMem,
        freeMem,
        systemTime,
        systemSleep,
        systemRand,
        initialMutex,
        lockMutex,
        unlockMutex,
        deInitialMutex,
        initialNet,
        setNetOptions,
        netConnect,
        recvData,
        sendData,
        deInitialNet
};